/*
 * Copyright 2009 XueSong Guo.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.webwheel.compiler;

import javassist.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JAJavaC extends JavaC {

    private MyClassLoader classLoader;

    private ClassPool classPool = new ClassPool(true);

    private ClassLoader parentClassLoader;

    public JAJavaC(ClassLoader parentClassLoader) {

        if (parentClassLoader == null) parentClassLoader = getClass().getClassLoader();

        this.parentClassLoader = parentClassLoader;

        classLoader = new MyClassLoader(parentClassLoader);

        classPool.insertClassPath(new LoaderClassPath(parentClassLoader));
    }


    @Override
    public Class compile(String name, String source) {

        if (debug) {
            System.err.println("using javassist");
            System.err.println("compiling: " + name);
            System.err.println(source);
        }

        Class lcls = null;

        while (true) {
            try {
                return compile0(name, source);
            } catch (Exception e) {
                //hack xx classloader
                if (e instanceof CannotCompileException) {
                    String s = "[source error] no such class: ";
                    String m = e.getMessage();
                    if (m.startsWith(s)) {
                        Class cls;
                        try {
                            cls = parentClassLoader.loadClass(m.substring(s.length()));
                        } catch (ClassNotFoundException e1) {
                            try {
                                cls = parentClassLoader.loadClass("java.lang." + m.substring(s.length()));
                            } catch (ClassNotFoundException e2) {
                                throw new CompileException(e);
                            }
                        }
                        if (lcls == cls) {
                            throw new CompileException(e);
                        } else {
                            classPool.insertClassPath(new ClassClassPath(cls));
                            continue;
                        }
                    }
                }
                if (debug) {
                    e.printStackTrace();
                }
                throw new CompileException(e);
            }
        }
    }

    private Class compile0(String name, String source) throws CannotCompileException, NotFoundException {

        source = source.trim();

        Map<String, StringBuilder> smap = new HashMap<String, StringBuilder>();

        {
            StringBuilder sb = new StringBuilder();

            StringBuilder sv = null;
            for (int i = 0; i < source.length(); i++) {
                char c = source.charAt(i);
                if (c == '\\') {
                    (sv != null ? sv : sb).append('\\').append(source.charAt(++i));
                    continue;
                }
                if (c == '"') {
                    if (sv == null) {
                        String k = "[!-" + i + "-!]";
                        smap.put(k, sv = new StringBuilder().append(c));
                        sb.append(k);
                    } else {
                        sv.append(c);
                        sv = null;
                    }
                } else if (sv == null) {
                    sb.append(c);
                } else {
                    sv.append(c);
                }
            }

            source = sb.toString();
        }

        {// imports
            int pidx = source.indexOf("public");
            String[] ss = source.substring(0, pidx).split(";");
            for (String s : ss) {
                s = s.trim();
                if (s.startsWith("import ")) {
                    s = s.substring("import ".length(), s.lastIndexOf('.')).trim();
                    classPool.importPackage(s);
                }
            }
            source = source.substring(pidx);
            classPool.importPackage("cn.webwheel.gen");
        }

        CtClass ctCls = classPool.makeClass("cn.webwheel.gen." + name);

        {// extends implements
            String s = source.substring(0, source.indexOf('{'));

            int ei = s.indexOf("extends ");
            int ii = s.indexOf("implements ");

            String es = null;
            String is = null;
            if (ii == -1) {
                if (ei != -1) {
                    es = s.substring(ei + "extends ".length());
                }
            } else if (ei == -1) {
                if (ii != -1) {
                    is = s.substring(ii + "implements ".length());
                }
            } else if (ii > ei) {
                es = s.substring(ei + "extends ".length(), ii);
                is = s.substring(ii + "implements ".length());
            } else {
                is = s.substring(ii + "implements ".length(), ei);
                es = s.substring(ei + "extends ".length());
            }
            if (es != null) {
                ctCls.setSuperclass(classPool.get(es.trim()));
            }
            if (is != null) {
                String[] ss = is.split(",");
                CtClass[] cs = new CtClass[ss.length];
                for (int i = 0; i < ss.length; i++) {
                    cs[i] = classPool.get(ss[i].trim());
                }
                ctCls.setInterfaces(cs);
            }
            source = source.substring(source.indexOf('{') + 1, source.length() - 1);
        }

        {
            StringBuilder sb = new StringBuilder();
            int i = 0;
            int j = 0;
            Pattern pat = Pattern.compile("\\[!\\-(\\d+)\\-!\\]");
            List<String> ms = new ArrayList<String>();
            for (; i < source.length(); i++) {
                char c = source.charAt(i);
                sb.append(c);
                if (c == '{') {
                    j++;
                } else if (c == '}') {
                    j--;
                    if (j == 0) {
                        String s = sb.toString();
                        Matcher matcher = pat.matcher(s);
                        sb = new StringBuilder();
                        int end = 0;
                        while (matcher.find()) {
                            sb.append(s.substring(end, matcher.start()));
                            sb.append(smap.get(matcher.group()));
                            end = matcher.end();
                        }
                        sb.append(s.substring(end));

                        ms.add(sb.toString());

                        sb = new StringBuilder();
                    }
                } else if (c == ';' && j == 0) {
                    ctCls.addField(CtField.make(sb.toString(), ctCls));
                    sb = new StringBuilder();
                }
            }
            for (String m : ms) {
                ctCls.addMethod(CtMethod.make(m, ctCls));
            }
        }

        return ctCls.toClass(classLoader, null);
    }
}
